package org.luxor.commons.workflow.component.cmd;

import com.fasterxml.jackson.databind.node.ArrayNode;
import org.activiti.bpmn.model.*;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.TaskService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.TaskActivityBehavior;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.apache.commons.lang.StringUtils;
import org.luxor.commons.core.utils.ThreadUtils;
import org.luxor.commons.core.utils.UuidUtils;
import org.luxor.commons.workflow.component.mapper.CustomExecutionMapper;
import org.luxor.commons.workflow.constant.TransientVariableConst;
import org.luxor.commons.workflow.constant.VariableConst;
import org.luxor.commons.workflow.entity.CustomExecutionEntity;
import org.luxor.commons.workflow.utils.ProcessUtils;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 任务撤回(召回)
 *
 * @author Mr.yan  @date 2021/10/28
 */
public class RevokeTaskCmd implements Command<Void>, Serializable {
    private static final long serialVersionUID = 1L;

    private final String collectionElementIndexVariable = "loopCounter";

    /**
     * 已办的任务
     */
    private String taskId;

    /**
     * 办理人
     */
    private String assignee;

    /**
     * 撤回原因
     */
    private String reason;


    public RevokeTaskCmd(String taskId, String assignee) {
        this(taskId, assignee, null);
    }

    public RevokeTaskCmd(String taskId, String assignee, String reason) {
        this.taskId = taskId;
        this.assignee = assignee;
        this.reason = reason;
    }

    @Override
    public Void execute(CommandContext commandContext) {

        // 目标已办任务
        HistoricTaskInstance doneTask = getDoneHistoricTaskInstance(commandContext, this.taskId);
        Activity doneActivity = getActivity(doneTask.getProcessDefinitionId(), doneTask.getTaskDefinitionKey());

        // 范围保护
        if (StringUtils.isNotBlank(this.assignee) && StringUtils.isNotBlank(doneTask.getAssignee())) {
            if (!StringUtils.equals(doneTask.getAssignee(), this.assignee)) {
                throw new ActivitiException("该任务不属于当前办理人(" + assignee + "). HistoricTaskInstanceId: " + taskId);
            }
        }

        // 待办任务列表
        List<TaskEntity> todoTaskList = findTodoTask(commandContext, doneTask.getProcessInstanceId());
        if (CollectionUtils.isEmpty(todoTaskList)) {
            throw new ActivitiException("该流程已办结,无法撤回.");
        }

        // 满足单步撤回要求？
        List<Activity> nextActivityList = ProcessUtils.nextActivityList(doneTask.getProcessDefinitionId(), doneTask.getTaskDefinitionKey());
        for (TaskEntity todoTask : todoTaskList) {
            if (doneActivity.getId().equals(todoTask.getTaskDefinitionKey())) {
                if (doneActivity.getBehavior() instanceof ParallelMultiInstanceBehavior) {
                    continue;
                }
                if (doneActivity.getBehavior() instanceof SequentialMultiInstanceBehavior) {
                    List<HistoricTaskInstance> historicTaskInstanceList = commandContext.getProcessEngineConfiguration().getHistoryService().createHistoricTaskInstanceQuery()
                            .processInstanceId(doneTask.getProcessInstanceId())
                            .taskDefinitionKey(doneActivity.getId())
                            .finished().orderByHistoricTaskInstanceStartTime().desc().list();
                    if (CollectionUtils.isEmpty(historicTaskInstanceList)) {
                        continue;
                    }
                    HistoricTaskInstance lastHistoricTaskInstance = historicTaskInstanceList.get(0);
                    if (lastHistoricTaskInstance.getId().equals(doneTask.getId())) {
                        continue;
                    }
                    throw new ActivitiException("任务已办理,无法撤回.");
                }
            }
            boolean isContain = false;
            for (Activity nextActivity : nextActivityList) {
                if (todoTask.getTaskDefinitionKey().equals(nextActivity.getId())) {
                    isContain = true;
                }
            }
            if (!isContain) {
                throw new ActivitiException("任务已办理,无法撤回.");
            }
        }

        // 如果已办节点是多实例，则收集并行实例任务中的[已办会签]
        List<String> multiInstanceDoneTaskAssignee = null;
        if (doneActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            multiInstanceDoneTaskAssignee = commandContext.getProcessEngineConfiguration()
                    .getHistoryService().createHistoricTaskInstanceQuery()
                    .processInstanceId(doneTask.getProcessInstanceId())
                    .taskDefinitionKey(doneTask.getTaskDefinitionKey())
                    .finished().list().stream().sorted(Comparator.comparing(HistoricTaskInstance::getCreateTime))
                    .map(HistoricTaskInstance::getAssignee).collect(Collectors.toList());
            multiInstanceDoneTaskAssignee.remove(assignee);
        }

        // 如果已办节点属于并行网关之内的节点，则收集并行网关内的[已办任务]
        List<ParallelGateway> parallelGatewayOutgoingFlow = getParallelGatewayOutgoingFlow(doneActivity);
        if (!CollectionUtils.isEmpty(parallelGatewayOutgoingFlow)) {
            for (ParallelGateway parallelGateway : parallelGatewayOutgoingFlow) {
                // 查询有几个已流转的任务
                List<HistoricActivityInstance> historicActivityInstanceList = commandContext.getProcessEngineConfiguration()
                        .getHistoryService().createHistoricActivityInstanceQuery()
                        .processInstanceId(doneTask.getProcessInstanceId())
                        .activityId(parallelGateway.getId())
                        .list();
                for (int i = 0; i < historicActivityInstanceList.size(); i++) {
                    HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
                    if (historicActivityInstance.getActivityId().equals(doneActivity.getId())) {
                        historicActivityInstanceList.remove(i);
                    }
                }
                if (historicActivityInstanceList.size() > 0) {
                    historicActivityInstanceList.remove(0);
                }
                for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
                    CustomExecutionEntity execution = new CustomExecutionEntity();
                    execution.setId(UuidUtils.uuid());
                    execution.setProcessInstanceId(historicActivityInstance.getProcessInstanceId());
                    execution.setParentId(historicActivityInstance.getProcessInstanceId());
                    execution.setProcessDefinitionId(historicActivityInstance.getProcessDefinitionId());
                    execution.setRootProcessInstanceId(historicActivityInstance.getProcessInstanceId());
                    execution.setActivityId(historicActivityInstance.getActivityId());
                    execution.setActive(false);
                    execution.setTenantId(historicActivityInstance.getTenantId());
                    execution.setStartTime(historicActivityInstance.getStartTime());
                    commandContext.getDbSqlSession().getCustomMapper(CustomExecutionMapper.class).createExecution(execution);
                }
            }
        }

        // 处理待办任务
        for (TaskEntity todoTask : todoTaskList) {
            Activity todoActivity = getActivity(todoTask.getProcessDefinitionId(), todoTask.getTaskDefinitionKey());
            ExecutionEntity execution = todoTask.getExecution();
            if (todoActivity.getBehavior() instanceof TaskActivityBehavior) {
                // 删除当前节点任务
                commandContext.getTaskEntityManager().delete(todoTask.getId());
                commandContext.getIdentityLinkEntityManager().deleteIdentityLinksByTaskId(todoTask.getId());
                commandContext.getHistoryManager().recordTaskEnd(todoTask.getId(), "revoke to:" + doneActivity.getId());
                commandContext.getHistoryManager().recordActivityEnd(execution, "revoke to:" + doneActivity.getId());
            } else if (todoActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
                ExecutionEntity rootExecutionEntity = getMultiInstanceRootExecution(execution);
                if (rootExecutionEntity != null) {
                    commandContext.getExecutionEntityManager().deleteChildExecutions(rootExecutionEntity, "revoke to:" + doneActivity.getId(), false);
                }
            }
        }

        // 触发流转
        ExecutionEntity execution = todoTaskList.get(0).getExecution();
        Activity todoActivity = getActivity(todoTaskList.get(0).getProcessDefinitionId(), todoTaskList.get(0).getTaskDefinitionKey());

        // 从多实例节点撤回
        if (todoActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            ExecutionEntity rootExecution = getMultiInstanceRootExecution(execution);
            removeLocalLoopVariable(rootExecution, collectionElementIndexVariable);
            rootExecution.setActive(true);
            rootExecution.setMultiInstanceRoot(false);

            commandContext.getExecutionEntityManager().update(execution);
            execution = rootExecution;
        }

        // 从子流撤回到父流程
        if (todoActivity.getParentContainer() instanceof SubProcess && execution.getParent() != null) {
            String subProcessId = ((SubProcess) todoActivity.getParentContainer()).getId();
            Map<String, FlowElement> flowElementMap = doneActivity.getParentContainer().getFlowElementMap();
            if (flowElementMap != null && flowElementMap.get(subProcessId) != null) {
                execution = execution.getParent();
            }
        }

        //父流程撤回到子流程
        if (doneActivity.getParentContainer() instanceof SubProcess) {
            SubProcess subProcess = (SubProcess) doneActivity.getParentContainer();
            Map<String, FlowElement> flowElementMap = todoActivity.getParentContainer().getFlowElementMap();
            if (flowElementMap.get(subProcess.getId()) != null) {
                FlowElement subStartEvents = subProcess.getFlowElements().stream().filter(el -> el instanceof StartEvent).findFirst().orElse(null);
                if (subStartEvents == null) {
                    throw new ActivitiException("No initial activity found for subprocess " + subProcess.getId());
                }
                execution.setScope(true);
                execution = Context.getCommandContext().getExecutionEntityManager().createChildExecution(execution);
            }
        }

        // 设置参数
        if (doneActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            List<String> assigneeList = getAssigneeList(execution);
            execution.setVariable(VariableConst.ASSIGNEE_LIST, assigneeList);
        } else {
            execution.setTransientVariable(TransientVariableConst.ASSIGNEE, assignee);
        }

        execution.setCurrentFlowElement(doneActivity);
        commandContext.getAgenda().planContinueProcessInCompensation(execution);

        // 如果撤回节点属于多实例，则补充完成已办会签任务
        if (doneActivity.getBehavior() instanceof MultiInstanceActivityBehavior && multiInstanceDoneTaskAssignee != null) {
            List<String> finalMultiInstanceDoneTaskAssignee = multiInstanceDoneTaskAssignee;
            new Thread(() -> {
                // 自动完成已办会签任务
                ThreadUtils.sleepQuietly(1000);
                for (String doneTaskAssignee : finalMultiInstanceDoneTaskAssignee) {
                    TaskEntity todoTask = getTodoTask(commandContext, doneTask.getProcessInstanceId(), doneActivity.getId(), doneTaskAssignee);
                    if (todoTask != null && doneTaskAssignee.equals(todoTask.getAssignee())) {
                        commandContext.getProcessEngineConfiguration().getTaskService().complete(todoTask.getId());
                    }
                }
            }).start();
        }
        return null;
    }

    /**
     * 获取输出目标的并行网关的节点
     *
     * @param doneActivity 已办任务节点
     * @return boolean
     */
    private List<ParallelGateway> getParallelGatewayOutgoingFlow(Activity doneActivity) {
        List<ParallelGateway> forkParallelGatewayList = new ArrayList<>();
        List<FlowElement> doneActivityTargetFlowElement = doneActivity.getOutgoingFlows().stream()
                .map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList());
        for (FlowElement targetFlowElement : doneActivityTargetFlowElement) {
            if (targetFlowElement instanceof ParallelGateway) {
                forkParallelGatewayList.add((ParallelGateway) targetFlowElement);
            }
        }
        return forkParallelGatewayList;
    }


    /**
     * 获取已办任务
     *
     * @param taskId 已办任务ID
     * @return org.activiti.engine.history.HistoricTaskInstance
     */
    private HistoricTaskInstance getDoneHistoricTaskInstance(CommandContext commandContext, String taskId) {
        HistoricTaskInstance doneTask = commandContext.getHistoricTaskInstanceEntityManager().findById(taskId);
        if (doneTask == null) {
            throw new ActivitiObjectNotFoundException("No historic task exists with the given id: " + taskId, HistoricTaskInstance.class);
        }
        return doneTask;
    }

    /**
     * 待办任务列表
     *
     * @param processInstanceId 流程实例ID
     */
    private List<TaskEntity> findTodoTask(CommandContext commandContext, String processInstanceId) {
        TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
        List<TaskEntity> currentTask = taskEntityManager.findTasksByProcessInstanceId(processInstanceId);
        if (CollectionUtils.isEmpty(currentTask)) {
            throw new ActivitiObjectNotFoundException("流程已结束，无法撤回");
        }
        return currentTask;
    }

    private Activity getActivity(String processDefinitionId, String taskDefKey) {
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId);
        return (Activity) bpmnModel.getFlowElement(taskDefKey);
    }

    private TaskEntity getTodoTask(CommandContext commandContext, String processInstanceId, String taskDefinitionKey,
                                   String userIdForCandidateAndAssignee) {
        TaskService taskService = commandContext.getProcessEngineConfiguration().getTaskService();
        return (TaskEntity) taskService.createTaskQuery().processInstanceId(processInstanceId)
                .taskDefinitionKey(taskDefinitionKey)
                .taskCandidateOrAssigned(userIdForCandidateAndAssignee)
                .active()
                .singleResult();
    }

    private List<String> getAssigneeList(ExecutionEntity execution) {
        List<String> assigneeList = new ArrayList<>();
        ArrayNode arrayNodes = (ArrayNode) execution.getVariable(VariableConst.ASSIGNEE_LIST);
        if (arrayNodes == null) {
            return assigneeList;
        }
        for (int i = 0; i < arrayNodes.size(); i++) {
            String assigneeText = arrayNodes.get(i).asText();
            assigneeList.add(assigneeText);
        }
        return assigneeList;
    }

    private Object getLoopVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        DelegateExecution parent = execution.getParent();
        while (value == null && parent != null) {
            value = parent.getVariableLocal(variableName);
            parent = execution.getParent();
        }
        return value == null ? 0 : (Integer) value;
    }

    private void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    private void removeLocalLoopVariable(DelegateExecution execution, String variableName) {
        execution.removeVariableLocal(variableName);
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }
}